import com.google.appengine.api.datastore.*; 
import java.util.logging.Logger

public class DAO {
	static { 
		// enhancing Entity
		Entity.metaClass.getProperty = {name ->
		    if (name == "id") {
		        return delegate.key.id
		    }
		    
		    def metaProperty = Entity.metaClass.getMetaProperty(name) 
		    metaProperty ? metaProperty.getProperty(delegate) : delegate.getProperty(name)
		}

		Entity.metaClass.setProperty = {name, value ->
		    if (name == "id") {
		        throw new IllegalStateException("can't set id")
		    }
		    def metaProperty = Entity.metaClass.getMetaProperty(name) 
		    metaProperty ? metaProperty.setProperty(delegate, value) : delegate.setProperty(name, value)
		}				
	}

	private log
	private dataStore = DatastoreServiceFactory.getDatastoreService()
	private kind
	
	public DAO(String kind) {
		this.kind = kind
		log = Logger.getLogger(this.class.name)
	}
	
	def load(id) {
		log.info("loading ${kind} #${id}")
	    return dataStore.get(KeyFactory.createKey(kind, id.toLong()))
	}

	def save(e) {
		log.info("saving ${kind} ${e}")
		dataStore.put(e)
	}
	
	def delete(id) {
		log.info("deleting ${kind} #${id}")
		dataStore.delete(KeyFactory.createKey(kind, id.toLong()))    
	}
	
	def newEntity() {
		log.info("creating new ${kind}")
		return new Entity(kind)
	}	
	
	def count() {
		log.info("counting ${kind}")
	    def q = new Query(kind)
	    return dataStore.prepare(q).countEntities()
	}

	def listAll() {
		log.info("listing all items of kind ${kind}...")
	    def q = new Query(kind)
	    return dataStore.prepare(q).asList(FetchOptions.Builder.withChunkSize(FetchOptions.DEFAULT_CHUNK_SIZE))
	}
	
	def methodMissing(String name, args) {
	    def m = name =~ /findBy([A-Z])(.+)/

	    if (m.matches()) {
	    	def propertyName = m[0][1].toLowerCase() + m[0][2]
	    	log.info("finding ${kind} by ${propertyName}=${args[0]}")
	    	def q = new Query(kind).addFilter(propertyName, Query.FilterOperator.EQUAL, args[0])
	    	return dataStore.prepare(q).asList(FetchOptions.Builder.withChunkSize(FetchOptions.DEFAULT_CHUNK_SIZE))
	    	
	    } else {
	    	throw new MissingMethodException(name, delegate, args)
	    }
	 }
}